package cn.yangjunda.servlet;

import cn.yangjunda.MVCException;
import cn.yangjunda.annotation.*;
import cn.yangjunda.multipart.MultipartResolver;
import cn.yangjunda.multipart.commons.CommonsMultipartResolver;
import cn.yangjunda.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.util.*;
import java.util.Map.Entry;


/**请求几种处理类
 * @author juanda
 *
 */
public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1378531571714153483L;
	
	/** 要扫描的包，只有在这个包下并且加了注解的才会呗扫描到 */
	private static String PACKAGE = null;
	
	private static final String CONTROLLER_KEY = "controller";
	
	private static final String METHOD_KEY = "method";

	private static final String ENTRY_KEY = "entry";
	
	/** 存放Controller中url和方法的对应关系，格式：{url:{controller:实例化后的对象,method:实例化的方法}} */
	private static Map<String, Map<String, Object>> urlMethodMapping = new HashMap<String, Map<String, Object>>();

	private Map<String, HandlerModel> handlerMapping = new HashMap<>();

	private Set<Class<?>> classes;

	private Configuration configuration;

	private MultipartResolver multipartResolver;

    private final Log logger = LogFactory.getLog(getClass());

	public DispatcherServlet() {  
        super();  
    } 
	
	/**
	 * 初始化方法，用于实例化扫描到的对象，并做注入和url映射（注：该方法逻辑上已经判断了，只执行一次）
	 */
	@Override
	public void init(ServletConfig config) {

		// 只处理一次
		if (urlMethodMapping.size() > 0) {
			return;
		}
		long startTime=System.currentTimeMillis();
        logger.debug("cn.yangjunda.servlet.DispatcherServlet  init");
		logger.debug("juanda-mvc 初始化开始");

        readConfiguration();
		// 存放Controller和Service的Map，格式：{beanName:实例化后的对象}
		Map<String, Object> instanceNameMap = new HashMap<String, Object>();
		// 存放Service接口类型与接口实例对象的Map，格式：{Service.instance.class:实现类实例化后的对象} 
		Map<Class<?>, Object> instanceTypeMap = new HashMap<Class<?>, Object>();
		
		// 组装instanceMap
		buildInstanceMap(classes, instanceNameMap, instanceTypeMap);
		
		// 开始注入
		doIoc(instanceNameMap, instanceTypeMap);
		// 注入完之后开始映射url和method
		buildUrlMethodMapping(instanceNameMap, urlMethodMapping);

		multipartResolver = new CommonsMultipartResolver();
		long endTime=System.currentTimeMillis();
        logger.debug("cn.yangjunda.servlet.DispatcherServlet  init");
        logger.debug("juanda-mvc 初始化成功 持续时间："+(endTime-startTime)+"ms");
    }
	
	@Override  
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
        this.doPost(req, resp);  
    }  
  
    @Override  
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {

    	String finallyUrl = resolveURL(req);
    	
    	// 取出这个url对应的Controller和method
    	Map<String, Object> map = urlMethodMapping.get(finallyUrl);
    	if (map != null) {
			Method method = (Method) map.get(METHOD_KEY);
			Entry<String, Object> entry = (Entry<String, Object>) map.get(ENTRY_KEY);
			Class<?> clazz = entry.getValue().getClass();
			if(!handleHttpRequestMethod(req,resp,method)){
				return;
			}
			Object object;
			try {
				String[] paramNames = ASMGetRealNameUtil.getMethodParameterNamesByAsm4(clazz,method);
				// 封装需要注入的参数
				List<Object> paramValue = buildParamObject(req, resp, method,paramNames);

				// 没有参数的场合
				if (paramValue.size() == 0) {
					object = method.invoke(map.get(CONTROLLER_KEY));
				}else {
					// 有参数的场合
					object = method.invoke(map.get(CONTROLLER_KEY), paramValue.toArray());
				}
				if(method.isAnnotationPresent(JuandaResponseBody.class)){
					ResponseJsonUtil.json(resp,object);
				}
			} catch (Exception e) {
				throw new RuntimeException("juanda-mvc 执行url对应的method失败！", e);
			}


		}else {
//			if(Objects.equals(url, "/") ||path.contains("html")||path.contains("xhtml")||path.contains("jsp")){
//				return;
//			}else {
//				throw new RuntimeException("请求地址不存在！");
//			}
		}

    }

    private void readConfiguration() {
        XMLReader xmlReader = new XMLReader();
        xmlReader.readXML();
        configuration = xmlReader.getConfiguration();
        logger.debug("cn.yangjunda.servlet.DispatcherServlet  init");
        logger.debug("juanda-mvc 读取XML配置文件");
        PACKAGE = configuration.getPackagePath();
        if(PACKAGE==null){
            logger.debug(" cn.yangjunda.servlet.DispatcherServlet  init");
            logger.debug("INFO: juanda-mvc 读取properties配置文件");
            PACKAGE = PropertiesUtil.getProperty("packagePath");
        }
        if(StringUtils.isEmpty(PACKAGE)){
            throw new RuntimeException("配置文件中不存在packagePath");
        }
        // 开始扫描包下全部class文件
        logger.debug("cn.yangjunda.servlet.DispatcherServlet  init");
        logger.debug("juanda-mvc 开始扫描包");
        classes = ClassTools.getClasses(PACKAGE);
    }

    private String resolveURL(HttpServletRequest req) {
        // 完整路径
        String url = req.getRequestURI();
        // 跟路径
        String path = req.getContextPath();
        // 计算出method上配置的路径,如果用户写了多个"///"，只保留一个
        return url.replace(path, "").replaceAll("/+", "/");
    }

    /**
     * 封装需要注入的参数
     * @param req
     * @param resp
     * @param method
     * @return
     */
	private List<Object> buildParamObject(HttpServletRequest req, HttpServletResponse resp, Method method, String[] paramNames) {
		
		// 封装需要注入的参数，目前只支持request和response以及加了@RequestParam标签的基本数据类型的参数注入
		Parameter[] parameters = method.getParameters();
		List<Object> paramValue = new ArrayList<Object>();
		if (parameters == null || parameters.length <= 0) {
		    return paramValue;
        }
		int i = 0;
		for (Parameter parameter : parameters) {
			// 当前参数有别名注解并且别名不为空
			if(parameter.isAnnotationPresent(RequestParam.class) && !parameter.getAnnotation(RequestParam.class).value().isEmpty()){
				paramValue.add(makeRequestParam(req, parameter));
			}else if(parameter.isAnnotationPresent(RequestObject.class) && !parameter.getAnnotation(RequestObject.class).value().isEmpty()){
                paramValue.add(makeObject(req, parameter));
			}else if (parameter.getParameterizedType().getTypeName().contains("HttpServletRequest")) {
				paramValue.add(req);
			}else if (parameter.getParameterizedType().getTypeName().contains("HttpServletResponse")) {
				paramValue.add(resp);
			}else if (parameter.getParameterizedType().getTypeName().contains("HttpSession")) {
				paramValue.add(req.getSession());
			}else if (parameter.getParameterizedType().getTypeName().contains("MultipartFile")) {
				paramValue.add(multipartResolver.resolveMultipart(req));
			}else{
				paramValue.add(makeNoAnnotationParam(req, parameter, paramNames, i));
			}
			i++;
		}

		return paramValue;
	}  

	/**
	 * 注入完之后开始映射url和method
	 * @param instanceMap
	 * @param urlMethodMapping
	 */
	private void buildUrlMethodMapping(Map<String, Object> instanceMap,
			Map<String, Map<String, Object>> urlMethodMapping) {
		// 注入完之后开始映射url和method
		// 组装urlMethodMapping
		for (Entry<String, Object> entry : instanceMap.entrySet()) {
			
			// 迭代出所有的urlinstanceMap
			String parentURL = "";
			
			// 判断Controller上是否加了requestMapping
			if (entry.getValue().getClass().isAnnotationPresent(JuandaRequestMapping.class)) {
				parentURL = entry.getValue().getClass().getAnnotation(JuandaRequestMapping.class).value();
			}
			// 取出全部的method
			Method[] methods = entry.getValue().getClass().getMethods();
			
			// 迭代全部的方法，检查哪些方法上加了requestMaping注解
			for (Method method : methods) {
				if (method.isAnnotationPresent(JuandaRequestMapping.class)) {
					StringBuilder sb = new StringBuilder();
					for(int i = 0; i < method.getParameters().length; i++){
						if(i!=method.getParameters().length-1){
							sb.append(method.getParameters()[i]).append(",");
						}else {
							sb.append(method.getParameters()[i]);
						}
					}
					// 得到一个完整的url请求
					String url = parentURL + "/" + method.getAnnotation(JuandaRequestMapping.class).value();
					url = url.replaceAll("/+", "/");
					logger.debug(" cn.yangjunda.servlet.DispatcherServlet  buildUrlMethodMapping");
					logger.debug("Mapped \"{["+url+"],methods=["+method.getAnnotation(JuandaRequestMapping.class).method().toString()+"]}\" onto public "+method.getName()+" ("+sb.toString()+")");
					Map<String, Object> value = new HashMap<>();
					value.put(CONTROLLER_KEY, entry.getValue());
					value.put(METHOD_KEY, method);
					value.put(ENTRY_KEY,entry);
					urlMethodMapping.put(url, value );
				}
			}
		}
	}

	/**
	 * 根据实例Map开始注入
	 * @param instanceMap
	 */
	private void doIoc(Map<String, Object> instanceMap, Map<Class<?>, Object> instanceTypeMap) {
        Map<String, Object> map = configuration.getBeans();
        for (Entry<String, Object> entry : map.entrySet()) {
                instanceTypeMap.put(entry.getValue().getClass(), entry.getValue());

        }
		// 开始注入，我们只对加了@Controller和@Service标签中的，属性加了@autowired的进行注入操作
		for (Entry<String, Object> entry : instanceMap.entrySet()) {

			// 取出全部的属性
			Field[] fields = entry.getValue().getClass().getDeclaredFields();

			// 循环属性校验哪些是加了@autowired注解的
			for (Field field : fields) {
				field.setAccessible(true);// 可访问私有属性

				// 有注解的时候
				if (field.isAnnotationPresent(JuandaAutowired.class)) {

					// 没有配别名注入的时候
					if (field.getAnnotation(JuandaAutowired.class).value().isEmpty()) {
						// 直接获取
						try {
							// 根据类型来获取他的实现类
							Object object = instanceTypeMap.get(field.getType());
							field.set(entry.getValue(), object);
						} catch (IllegalArgumentException | IllegalAccessException e) {
							throw new RuntimeException("开始注入时出现了异常", e);
						}
					} else {
						try {
							// 将被注入的对象
							Object object = instanceMap.get(field.getAnnotation(JuandaAutowired.class).value());
							field.set(entry.getValue(), object);
						} catch (Exception e) {
							throw new RuntimeException("开始注入时出现了异常", e);
						}
					}
				}
			}
		}
	}

	/**
	 * 组装instanceMap
	 * @param classes
	 * @param instanceMap
	 */
	private void buildInstanceMap(Set<Class<?>> classes, Map<String, Object> instanceMap, Map<Class<?>, Object> instanceTypeMap) {
		// 开始循环全部class
		for (Class<?> clasz : classes) {
			
			// 组装instanceMap
			// 判断是否是是加了Controller注解的java对象
			if (clasz.isAnnotationPresent(JuandaController.class)) {
				try {
					// 实例化对象
					Object obj = clasz.newInstance();
					JuandaController controller = clasz.getAnnotation(JuandaController.class);
					
					// 如果没有设置别名，那么用类名首字母小写做key
					if (controller.value().isEmpty()) {
						instanceMap.put(firstLowerName(clasz.getSimpleName()), obj);
					}else{
						// 如果设置了别名那么用别名做key
						instanceMap.put(controller.value(), obj);
					}
				} catch (Exception e) {
					throw new RuntimeException("初始化instanceMap时在处理Controller注解时出现了异常", e);
				}				
			}else if(clasz.isAnnotationPresent(JuandaService.class)) {
				// 实例化对象
				Object obj = null;
				try {
					// 实例化对象
					obj = clasz.newInstance();
					JuandaService service = clasz.getAnnotation(JuandaService.class);
					
					// 如果没有设置别名，那么用类名首字母小写做key
					if (service.value().isEmpty()) {
						instanceMap.put(firstLowerName(clasz.getSimpleName()), obj);
					}else{
						// 如果设置了别名那么用别名做key
						instanceMap.put(service.value(), obj);
					}
				} catch (Exception e) {
					throw new RuntimeException("初始化instanceMap时在处理Service注解时出现了异常", e);
				}
				// 实现的接口数组
				Class<?>[] interfaces = clasz.getInterfaces();
				for (Class<?> class1 : interfaces) {
					if (instanceTypeMap.get(class1) != null) {
						throw new RuntimeException(class1.getName() + "接口不能被多个类实现！");
					}
					instanceTypeMap.put(class1, obj);
				}
			} else if(clasz.isAnnotationPresent(JuandaRepository.class)) {
                // 实例化对象
                Object obj = null;
                try {
                    // 实例化对象
                    obj = clasz.newInstance();
                    JuandaRepository repository = clasz.getAnnotation(JuandaRepository.class);

                    // 如果没有设置别名，那么用类名首字母小写做key
                    if (repository.value().isEmpty()) {
                        instanceMap.put(firstLowerName(clasz.getSimpleName()), obj);
                    }else{
                        // 如果设置了别名那么用别名做key
                        instanceMap.put(repository.value(), obj);
                    }
                } catch (Exception e) {
                    throw new RuntimeException("初始化instanceMap时在处理Repository注解时出现了异常", e);
                }
                // 实现的接口数组
                Class<?>[] interfaces = clasz.getInterfaces();
                for (Class<?> class1 : interfaces) {
                    if (instanceTypeMap.get(class1) != null) {
                        throw new RuntimeException(class1.getName() + "接口不能被多个类实现！");
                    }
                    instanceTypeMap.put(class1, obj);
                }
            } else {
				if(configuration.getAutowire()){
					Object obj = null;
					try {
						obj = clasz.newInstance();
						Class<?>[] interfaces = clasz.getInterfaces();
						for (Class<?> class1 : interfaces) {
							logger.debug("cn.yangjunda.servlet.DispatcherServlet  bean封装");
							logger.debug("juanda-mvc "+configuration.getBeans().get(class1.getName())+"->"+class1.getName());
							if(configuration.getBeans().get(class1.getName())!=null){
								if (instanceTypeMap.get(class1) != null) {
									throw new RuntimeException(class1.getName() + "接口不能被多个类实现！");
								}
								instanceTypeMap.put(class1, obj);
							}
						}
					} catch (InstantiationException | IllegalAccessException e) {
//					e.printStackTrace();
					}
				}
			}
		}
	}
    
    /**
     * 首字母小写
     * @param name
     * @return
     */
    private String firstLowerName(String name) {
        name = name.substring(0, 1).toLowerCase() + name.substring(1);
       return  name;
    }

	/**
	 * url请求拦截器
	 * @param req
	 * @param resp
	 * @param method
	 * @return
	 */
	private boolean handleHttpRequestMethod(HttpServletRequest req, HttpServletResponse resp ,Method method){
		if(method.getAnnotation(JuandaRequestMapping.class).method().length>0){
			RequestMethod[] requestMethods = method.getAnnotation(JuandaRequestMapping.class).method();
			boolean flag = false;
			for (int i = 0;i<requestMethods.length;i++){
			    if (req.getMethod() != null) {
                    if(req.getMethod().equals(requestMethods[i].toString())){
                        flag = true;
                    }
                }
			}
			if(!flag){
				logger.debug("cn.yangjunda.servlet.DispatcherServlet handleHttpRequestMethod");
				logger.warn("Request method '"+req.getMethod()+"' not supported");
				try {
					resp.sendError(405,"Request method '"+req.getMethod()+"' not supported");
				} catch (IOException e) {
					throw new RuntimeException("拦截请求异常");
				}finally {
					return false;
				}
			}
		}
		return true;
	}

	private Object makeRequestParam(HttpServletRequest req, Parameter parameter) {
        // 我们获取
        String value = req.getParameter(parameter.getAnnotation(RequestParam.class).value());

        if(value==null&&(!"\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n".equals(parameter.getAnnotation(RequestParam.class).defaultValue()))){
            value = parameter.getAnnotation(RequestParam.class).defaultValue();
        }
        if(parameter.getAnnotation(RequestParam.class).required()){
            if(value==null){
                throw new NullPointerException("字段："+parameter.getAnnotation(RequestParam.class).value()+"为必须的，值不能为空");
            }
        }
        Object o = null;
        try {
            assert value != null;
            o = adapter(parameter.getType(),value.trim(),req,parameter.getClass());
        } catch (InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        } catch (NullPointerException e){
        }
        return o;
    }

    private Object makeObject(HttpServletRequest req, Parameter parameter) {
        // 我们获取
        try {
            String classname = null;
            String name = null;
            String className = null;
            String paramsName = null;
            for (Class<?> clasz : classes) {
                name = clasz.getName();
                className = name.substring((clasz.getName().lastIndexOf(".") + 1), clasz.getName().length()).trim();
                paramsName= parameter.getAnnotation(RequestObject.class).value().trim().substring(0, 1).toUpperCase() + parameter.getAnnotation(RequestObject.class).value().substring(1);
                if(Objects.equals(className, paramsName)){
                    classname = clasz.getName();
                }
            }
            return AutoPackObjectUtil.getObject(req,Class.forName(classname));
        } catch (Exception e) {
            throw new RuntimeException("开始注入对象时出现了异常", e);
        }
    }

    private Object makeNoAnnotationParam(HttpServletRequest req, Parameter parameter, String[] paramNames, int i) {
        String value = req.getParameter(paramNames[i]);
        Object o = null;
        try {
            o = adapter(parameter.getType(),value.trim(),req,parameter.getClass());
        } catch (InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        } catch (NullPointerException e){
            o = null;
        }
        return o;
    }

	/**
	 * request参数自动封装
	 * @param paramType
	 * @param value
	 * @return
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 */
	private Object adapter(Class<?> paramType, String value,HttpServletRequest req,Class<?> paramClass) throws InvocationTargetException, IllegalAccessException {
		Object object = null;
		if (paramType == String.class) {
			object = value;
		} else if (paramType == Integer.class) {
			if(StringUtils.isBlank(value)){
				object=null;
			}else {
				object = Integer.valueOf(value);
			}
		} else if(paramType == int.class){
			object = Integer.parseInt(value);
		} else if (paramType == Long.class || paramType == long.class) {
			object = Long.parseLong(value);
		} else if (paramType == Boolean.class || paramType == boolean.class) {
			object = Boolean.parseBoolean(value);
		} else if (paramType == Short.class || paramType == short.class) {
			object = Short.parseShort(value);
		} else if (paramType == Float.class || paramType == float.class) {
			object = Float.parseFloat(value);
		} else if (paramType == Double.class || paramType == double.class) {
			object = Double.parseDouble(value);
		} else if (paramType == BigDecimal.class) {
			object = new BigDecimal(value);
		} else if (paramType == Character.class || paramType == char.class) {
			char[] cs = value.toCharArray();
			if (cs.length > 1) {
				throw new IllegalArgumentException("参数长度太大");
			}
			object = value.toCharArray()[0];
		} else{
			String className = null;
			for (Class<?> clasz : classes) {
				if(Objects.equals(clasz, paramClass)){
                    className = clasz.getName();
				}
			}
			try {
				object = AutoPackObjectUtil.getObject(req,Class.forName(className));
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
    	return object;
	}
}
